

#include <fstream>
#include <sstream>
#include <iomanip>

#include <boost/cerrno.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>

using boost::system::error_code;
using boost::system::system_category;
using boost::system::system_error;

#include "bocat/common/ini.hpp"
#include "bocat/common/logging.hpp"


#include "aucab.hpp"


namespace   fetcab   {


#define	N_CHECKPOINT_INTERVAL	1000000


Aucab::Aucab( const char* ini_filename, const char* ini_section )
		:	_bin_default_p(NULL)
{
	initialize( ini_filename, ini_section );

	_count_put_or_erase = 0;
}

Aucab::~Aucab()
{
	finalize();
}


///////////////////////////////////////////////////////////////////////
//

void
Aucab::initialize( const char* ini_filename, const char* ini_section )
{
	bocat::common::INIFile	ini( ini_filename );

	// get count of bin
	std::string	bin_count = ini[ ini_section ][ "bin.count" ];
	if (  bin_count.size() < 1 ) {
		std::ostringstream	msg;
		msg << "No option 'bin.count' for [" << ini_section << "] in " << ini_filename;
		throw std::runtime_error( msg.str() );
	}

	// get default setting for bin
	std::string	bin_default_section = ini[ ini_section ][ "bin.default" ];
	std::ostringstream	stream_ini;
	stream_ini << ini[ bin_default_section ];
	_bin_default_settings = stream_ini.str();

	std::string	default_section = ini[ bin_default_section ][ "section" ];
	if ( default_section.size() < 1 ) {
		std::ostringstream	msg;
		msg << "No option 'section' for [" << bin_default_section << "] in " << ini_filename;
		throw std::runtime_error( msg.str() );
	}
	std::string	default_dbhome = ini[ bin_default_section ][ "dbhome" ];
	if ( default_dbhome.size() < 1 ) {
		std::ostringstream	msg;
		msg << "No option 'dbhome' for [" << bin_default_section << "] in " << ini_filename;
		throw std::runtime_error( msg.str() );
	}

	boost::filesystem::path	default_dbhome_path( default_dbhome );
	if ( !boost::filesystem::exists( default_dbhome_path ) ) {
		try {
			boost::filesystem::create_directories( default_dbhome_path );
		}
		catch ( std::exception& e ) {
			std::ostringstream	msg;
			msg << "fail to create dbhome " << default_dbhome << " -- " << e.what();
			throw	std::runtime_error( msg.str() );
		}
	}

	// create default bin
	std::string	default_db_filename = ini[ ini_section ][ "dbfile" ];
	if ( default_db_filename.size() < 1 ) {
		std::ostringstream	msg;
		msg << "No option 'dbfile' for [" << ini_section << "] in " << ini_filename;
		throw std::runtime_error( msg.str() );
	}
	std::string	default_ini_filename = default_db_filename + ".ini";
	boost::filesystem::path	default_ini_filepath( default_ini_filename );
	if ( !boost::filesystem::exists( default_ini_filepath ) ) {
		std::ostringstream	stream;
		stream << ini[ ini_section ];

		std::ofstream	ofs( default_ini_filename.c_str() );
		if ( !ofs ) {
			std::ostringstream	msg;
			msg << "fail to create " << default_ini_filename;
			throw std::runtime_error( msg.str() );
		}

		ofs << "[" << default_section << "]" << std::endl
			<< stream.str() << std::endl
			<< std::endl;

		ofs.close();
	}
	_bin_default_p = new Aubin( default_ini_filename.c_str(), default_section.c_str() );

	// create other bin
	boost::trim( bin_count );
	unsigned	bin_count_i = boost::lexical_cast< unsigned >( bin_count.c_str() );
	for (unsigned i=0; i<bin_count_i; ++i) {
		std::ostringstream	stream_db;
		stream_db << default_dbhome << "/" << std::setw(bin_count.size()) << std::setfill('0') << i << ".db";

		std::string	bin_db = stream_db.str();
		std::string	bin_ini = bin_db + ".ini";

		boost::filesystem::path	ini_path( bin_ini );
		if ( !boost::filesystem::exists( ini_path ) ) {
			std::ofstream	ofs( bin_ini.c_str() );
			if ( !ofs ) {
				std::ostringstream	msg;
				msg << "fail to create " << bin_ini;
				throw std::runtime_error( msg.str() );
			}

			ofs << "[" << default_section << "]" << std::endl
				<< "dbfile = " << bin_db << std::endl
				<< _bin_default_settings << std::endl
				<< std::endl;

			ofs.close();
		}

		aubin_ptr	bin_p = new Aubin( bin_ini.c_str(), default_section.c_str() );
		_bins.push_back( bin_p );
	}
}

void
Aucab::finalize()
{
	// close all bins
	for( size_t i=0; i<_bins.size(); ++i ) {
		aubin_ptr&	bin_p = _bins[ i ];
		delete bin_p;
	}
	_bins.clear();

	// close default bin
	if (NULL != _bin_default_p) {
		delete _bin_default_p;
		_bin_default_p = NULL;
	}
}



///////////////////////////////////////////////////////////////////////
//


int 
Aucab::insert( cid_type   cid,
         const std::string& kpin,
         const std::string& key,
         const std::string& data )
{
    aubin_ptr   aubin_p = getBin( cid );

	++_count_put_or_erase;
	if (_count_put_or_erase > N_CHECKPOINT_INTERVAL) {
		_count_put_or_erase = 0;
	}   

    return  aubin_p->insert( kpin, key, data );
}

/*
int 
Aucab::insertAppend( cid_type   cid,
         const std::string& kpin,
         const std::string& key,
         const std::string& data )
{
    aubin_ptr   aubin_p = getBin( cid );

	++_count_put_or_erase;
	if (_count_put_or_erase > N_CHECKPOINT_INTERVAL) {
		_count_put_or_erase = 0;
	}   

    return  aubin_p->insertAppend( kpin, key, data );
}

int 
Aucab::insertIgnore( cid_type   cid,
         const std::string& kpin,
         const std::string& key,
         const std::string& data )
{
    aubin_ptr   aubin_p = getBin( cid );

	++_count_put_or_erase;
	if (_count_put_or_erase > N_CHECKPOINT_INTERVAL) {
		_count_put_or_erase = 0;
	}   

    return  aubin_p->insertIgnore( kpin, key, data );
}
*/


int 
Aucab::erase( cid_type cid,
           const std::string& kpin,
           const std::string& key )
{
    aubin_ptr   aubin_p = getBin( cid );

	++_count_put_or_erase;
	if (_count_put_or_erase > N_CHECKPOINT_INTERVAL) {
		_count_put_or_erase = 0;
	}   

    return  aubin_p->erase( kpin, key );
}

int 
Aucab::find( cid_type   cid,
         const std::string& kpin,
         const std::string& key,
               std::string& data )
{
    aubin_ptr   aubin_p = getBin( cid );

    return  aubin_p->find( kpin, key, data );
}


/*
int 
Aucab::find( cid_type   cid,
         const std::string& kpin,
         const std::string& key,
               std::vector< std::string >& data,
			   u_int32_t	limit )
{
    aubin_ptr   aubin_p = getBin( cid );

    return  aubin_p->find( kpin, key, data, limit );
}
*/


///////////////////////////////////////////////////////////////////////
//


/*
int
Aucab::insertAppend( cid_type cid,
				  const std::vector< std::string >& kpin_v,
				  const std::vector< std::string >& key_v,
				  const std::vector< std::string >& data_v )
{
    aubin_ptr   aubin_p = getBin( cid );

	_count_put_or_erase += key_v.size();
	if (_count_put_or_erase > N_CHECKPOINT_INTERVAL) {
		_count_put_or_erase = 0;
	}   

    return  aubin_p->insertAppend( kpin_v, key_v, data_v );
}
*/


///////////////////////////////////////////////////////////////////////
//


/*
int
Aucab::slim( void )
{
	int	count_slimmed = 0;

    bins_iterator_type  it = _bins.begin();
    while ( _bins.end() != it ) {
        aubin_ptr	bin_p = *it;

		int	rc = bin_p->slim();
		if ( rc < 0 ) {
			// something is wrong
		}
		else if ( rc > 0 ) {
			// number of record slimmed

			_count_put_or_erase += rc;
			count_slimmed += rc;
		}

		++it;
    }

	return	count_slimmed;
}
*/


///////////////////////////////////////////////////////////////////////
//


Aucab::aubin_ptr
Aucab::getBin( cid_type cid )
{
	if ( cid < 0 ) {
		return	_bin_default_p;
	}

	if ( _bins.size() < 1 ) {
		// no more bins, just go to default
		return	_bin_default_p;
	}

	unsigned 	cid_u = static_cast< unsigned >( cid );
	unsigned	index = cid_u % _bins.size();
	return	_bins[ index ];
}


///////////////////////////////////////////////////////////////////////
//





}   //// namespace fetcab


